import 'reflect-metadata';
import relationalStore from '@ohos.data.relationalStore';
import SqlBuilder from './SqlBuilder'
import { AnnotationEnum } from '../annotation/AnnotationEnum'
import { ColumnType } from './ColumnType';
import { TableStruct, TableStructCache } from './TableStructCache';
import { Condition } from './Condition';
import { OperatorType } from './OperatorType';
import PredicatesBuilder from './PredicatesBuilder';
import { RelTypeEnum } from '../annotation/RelTypeEnum';
import { CJLogUtil } from './CJLogUtil';
import { Context } from '@kit.AbilityKit';
import { ObjectUtil } from '../utils/ObjectUtil';
import { ValuesBucket } from '@kit.ArkData';

const TAG = "CJDBUtil"

export interface DBConfig {
  context: Context,
  dbName?: string,
  securityLevel?: relationalStore.SecurityLevel
}

class CJDBUtil {
  private static instance?: CJDBUtil
  // 获取context后调用getRdbStore
  private rdbStore: relationalStore.RdbStore | undefined = undefined

  private constructor() {
  }

  public static getSingleton(): CJDBUtil {
    if (!CJDBUtil.instance) {
      CJDBUtil.instance = new CJDBUtil();
    }
    return CJDBUtil.instance;
  }

  init(config: DBConfig): Promise<void> {
    CJLogUtil.MODULE = 0x0001
    return new Promise<void>((resolve, reject) => {
      relationalStore
        .getRdbStore(config.context, {
          name: config.dbName || 'cjdb.db',
          securityLevel: config.securityLevel || relationalStore.SecurityLevel.S1
        })
        .then(rdbStore => {
          CJLogUtil.info(TAG, "Get RdbStore successfully.")
          this.rdbStore = rdbStore
          resolve()
        })
        .catch((err) => {
          CJLogUtil.error(TAG, "Get RdbStore failed, err: " + JSON.stringify(err))
          reject(err)
        })
    })
  }

  /**
   * 根据指定SQL语句查询数据库中的数据，使用Promise异步回调。
   * @param sql: 如："SELECT * FROM test WHERE name = ?"
   * @param params 如：['zhangsan']
   * @returns
   */
  querySql(sql: string, params?: Array<relationalStore.ValueType>): Promise<relationalStore.ResultSet> {
    return this.rdbStore.querySql(sql, params)
  }

  /**
   * 查询T类所有数据，如果类使用@Observed修饰后，此时需要传入字符串“类名”,例如：Person被Observed修饰后，需要改成"Person"传入
   * 会导致类的结构发生变化，
   * 后续鸿蒙应该会修复
   * @param className
   * @param conditions
   * @param parse 自行解析函数
   * @returns
   */
  async query<T>(clazz: Function | string, conditions?: Condition[],
    parse?: (key: string, value: object) => object): Promise<T[]> {
    return new Promise<T[]>(async (resolve, reject) => {

      let className = undefined
      if (typeof clazz == "string") {
        className = clazz
      } else {
        className = clazz.name
      }
      // 获取表名
      const tableName = TableStructCache.tableMaps.get(className)
      const predicatesBuilder: PredicatesBuilder = new PredicatesBuilder(tableName)

      if (conditions && conditions.length > 0) {
        conditions.forEach((item: Condition) => {
          predicatesBuilder.addCondition(item.column, item.operatorType, item.values)
        })
      }

      // 检测表
      await this.checkClassTable(className)

      const structs = TableStructCache.structMaps.get(className)
      this.rdbStore
        .query(predicatesBuilder.build())
        .then(async (resultSet: relationalStore.ResultSet) => {
          CJLogUtil.info(TAG, "ResultSet row count: " + resultSet.rowCount)

          let rows = await this.parseResult<T>(resultSet, structs, parse)

          resultSet.close()
          resolve(rows)
        })
        .catch((err) => {
          CJLogUtil.info(TAG, "Query failed, err: " + err)
          reject(err)
        })
    })
  }

  /**
   * 查询T类所有数据，如果类使用@Observed修饰后，此时需要传入字符串“类名”,例如：Person被Observed修饰后，需要改成"Person"传入
   * 会导致类的结构发生变化，
   * 后续鸿蒙应该会修复
   * @param clazz
   * @param id
   * @returns
   */
  public async getById<T>(className: string, id: number | string,
    parse?: (key: string, value: any) => any): Promise<T | undefined> {
    // @ts-ignore
    return new Promise<T | undefined>(async (resolve, reject) => {

      // let className = undefined
      // if (typeof clazz == "string") {
      //   className = clazz
      // } else {
      //   // @ts-ignore
      //   className = clazz.name
      // }
      // 获取表名
      const tableName = TableStructCache.tableMaps.get(className)
      const structs = TableStructCache.structMaps.get(className)

      const predicatesBuilder: PredicatesBuilder = new PredicatesBuilder(tableName)

      structs.forEach((item: TableStruct) => {
        if (item.isId) {
          predicatesBuilder.addCondition(item.name, OperatorType.EQUAL_TO, [id])
        }
      })

      // 检测表
      await this.checkClassTable(className)

      this.rdbStore
        .query(predicatesBuilder.build())
        .then(async (resultSet) => {
          CJLogUtil.info(TAG, "ResultSet row count: " + resultSet.rowCount)
          let rows: T[] = await this.parseResult<T>(resultSet, structs, parse)
          resultSet.close()

          if (rows && rows.length > 0) {
            resolve(rows[0])
          } else {
            resolve(undefined)
          }
        })
        .catch((err) => {
          CJLogUtil.info(TAG, "Query failed, err: " + err)
          reject(err)
        })
    })

  }

  // TODO 完善中
  private async getByIdV2<T>(clazz: T, id: number, parse?: (key: string, value: any) => any): Promise<T> {
    // @ts-ignore
    return new Promise<T>(async (resolve, reject) => {

      // @ts-ignore
      const className = clazz.name
      // 获取表名
      const tableName = TableStructCache.tableMaps.get(className)
      const structs = TableStructCache.structMaps.get(className)


      // 进行检测，构建查询sql
      SqlBuilder.getQuerySql(className)


      // const predicatesBuilder: PredicatesBuilder = new PredicatesBuilder(tableName)
      //
      // structs.forEach((item: TableStruct) => {
      //   if (item.isId == true) {
      //     predicatesBuilder.addCondition(item.name, OperatorType.EQUAL_TO, [id])
      //   }
      // })

      // 检测表
      // await this.checkClassTable(className)

      // this.rdbStore
      //   .query(predicatesBuilder.build())
      //   .then(async (resultSet) => {
      //     CJLogUtil.info(TAG, "ResultSet row count: " + resultSet.rowCount)
      //     let rows: T[] = await this.parseResult(clazz, resultSet, structs, parse)
      //     resultSet.close()
      //
      //     if (rows && rows.length > 0) {
      //       resolve(rows[0])
      //     } else {
      //       resolve(null)
      //     }
      //   })
      //   .catch((err) => {
      //     CJLogUtil.info(TAG, "Query failed, err: " + err)
      //     reject(err)
      //   })
    })

  }

  /**
   * 查询T类所有数据，如果类使用@Observed修饰后，此时需要传入字符串“类名”,例如：Person被Observed修饰后，需要改成"Person"传入
   * 会导致类的结构发生变化，
   * 后续鸿蒙应该会修复
   * @param cls
   * @returns
   */
  public async all<T>(clazz: T | string, parse?: (key: string, value: any) => any): Promise<T[]> {
    return new Promise<T[]>(async (resolve, reject) => {

      let className = undefined
      if (typeof clazz == "string") {
        className = clazz
      } else {
        // @ts-ignore
        className = clazz.name
      }
      // 获取表名
      const tableName = TableStructCache.tableMaps.get(className)
      // 检测表
      await this.checkClassTable(className)

      const structs = TableStructCache.structMaps.get(className)

      this.rdbStore
        .query(new relationalStore.RdbPredicates(tableName))
        .then(async (resultSet) => {
          CJLogUtil.info(TAG, "ResultSet row count: " + resultSet.rowCount)
          let rows = await this.parseResult<T>(resultSet, structs, parse)
          resultSet.close()
          resolve(rows)
        })
        .catch((err) => {
          CJLogUtil.info(TAG, "Query failed, err: " + err)
          reject(err)
        })
    })
  }

  /**
   * 存储一组数据
   * @param list
   */
  public async saveList<T>(list: T[], remap?: (object) => object): Promise<number> {
    const className = list[0].constructor.name
    await this.checkClassTable(className)
    let results = JSON.parse(JSON.stringify(list))
    if (remap) {
      results = []
      list.forEach(item => {
        results.push(remap(JSON.parse(JSON.stringify(item))))
      })
    }
    for (let index = 0; index < results.length; index++) {
      this.savePreHandle(className, results[index], list[index])
    }

    return new Promise<number>((resolve, reject) => {
      return this.rdbStore.batchInsert(
        TableStructCache.tableMaps.get(className),
        // @ts-ignore
        results)
        .then(res => {
          resolve(res)
        })
        .catch(err => {
          reject(err)
        })
    })

  }

  /**
   * 直接指定类名进行存储，
   * @param clazz 类
   * @param list 满足 T 结构的列表
   * @param remap
   * @returns
   */
  public async saveListByClass<T>(clazz: T | string, list: object[], remap?: (object) => object): Promise<number> {
    // @ts-ignore
    let className = undefined
    if (typeof clazz == "string") {
      className = clazz
    } else {
      // @ts-ignore
      className = clazz.name
    }
    await this.checkClassTable(className)
    let results = list
    if (remap) {
      results = []
      list.forEach((item: object) => {
        results.push(remap(item))
      })
    }
    for (let index = 0; index < results.length; index++) {
      this.savePreHandle(className, results[index], list[index])
    }
    return new Promise<number>((resolve, reject) => {
      this.rdbStore.batchInsert(
        TableStructCache.tableMaps.get(className),
        // @ts-ignore
        results)
        .then(res => {
          resolve(res)
        })
        .catch(err => {
          reject(err)
        })
    })
  }

  /**
   * 保存
   * @param obj
   * @param remap 重映射，当对象属性与表属性不一致时使用，例如：任务重有坐标点属性为[number,number]时，没有对应基本数据类型，
   * 这时可以存储为Text类型，就需要使用到重映射,只需要保证存储的数据与表结构一致就行，同时需要配合查询的【parse】使用
   */
  public async save<T>(instance: T, remap?: (params: any) => any): Promise<number> {
    const className = instance.constructor.name
    await this.checkClassTable(className)

    let result = JSON.parse(JSON.stringify(instance))
    if (remap) {
      result = remap(result)
    }

    await this.savePreHandle(className, result, instance)

    return new Promise<number>((resolve, reject) => {
      this.rdbStore.insert(
        TableStructCache.tableMaps.get(className),
        // @ts-ignore
        result)
        .then(res => {
          resolve(res)
        })
        .catch(err => {
          reject(err)
        })
    })
  }

  /**
   * 存储前的预处理
   */
  private async savePreHandle<T>(className: string, temp: object, instance: T) {
    // 删除忽略属性
    let keys = Object.keys(instance)
    let result: object = {}
    keys.forEach((key) => {
      const ignore = Reflect.getMetadata(ColumnType.IGNORE, instance, key)
      // const column = Reflect.getMetadata(AnnotationEnum.COLUMN, instance, key)
      if (ignore && temp[key]) {
        // delete temp[key]
      } else {
        result[key] = temp[key]
      }
    })

    // 检测是否存在 对象 one2one one2many many2manay
    const structs = TableStructCache.structMaps.get(className)
    for (let index = 0; index < structs.length; index++) {
      const item = structs[index]
      if (item.relType == RelTypeEnum.ONE_TO_ONE) { // 一对一
        // 存储对象 获取对象id
        result[item.columnName] = await this.save(result[item.name])
        delete result[item.name]
      } else if (item.propertyType == "object" && item.type == ColumnType.TEXT) {
        // 当需要将对象存储为TEXT时，自动将其json格式
        if (result[item.name]) {
          result[item.name] = JSON.stringify(result[item.name])
        }
      }
    }

    temp = result
  }

  /**
   * 直接指定类名进行存储
   * 只需对象结构满足即可
   *
   * @param clazz 直接使用类，如果类使用@Observed修饰后，此时需要传入字符串“类名”,例如：Person被Observed修饰后，需要改成"Person"传入
   * 会导致类的结构发生变化，
   * 后续鸿蒙应该会修复
   * @param instance 满足 T 格式的 对象
   * @param remap
   * @returns
   */
  public async saveByClass<T>(clazz: T | string, instance: object,
    remap?: (obj: ValuesBucket) => ValuesBucket): Promise<number> {
    let className = undefined
    if (typeof clazz == "string") {
      className = clazz
    } else {
      // @ts-ignore
      className = clazz.name
    }
    await this.checkClassTable(className)
    // let tableName = instance[AnnotationEnum.TABLE]
    let result = ObjectUtil.clone(instance) as ValuesBucket
    if (remap) {
      result = remap(result)
    }

    // 检测是否存在 对象 one2one one2many many2manay
    await this.savePreHandle(className, result, instance)

    return new Promise<number>((resolve, reject) => {
      this.rdbStore.insert(
        TableStructCache.tableMaps.get(className),
        result)
        .then(res => {
          resolve(res)
        })
        .catch(err => {
          reject(err)
        })
    })
  }

  /**
   * 更新
   * @param clazz 直接使用类，如果类使用@Observed修饰后，此时需要传入字符串“类名”,例如：Person被Observed修饰后，需要改成"Person"传入
   * 会导致类的结构发生变化，
   * 后续鸿蒙应该会修复
   * @param values
   * @param conditions
   * @returns
   */
  public async update<T>(clazz: T | string, values, conditions?: Condition[]): Promise<number> {
    let className = undefined
    if (typeof clazz == "string") {
      className = clazz
    } else {
      // @ts-ignore
      className = clazz.name
    }

    const tableName = TableStructCache.tableMaps.get(className)
    await this.checkClassTable(className)

    const predicatesBuilder: PredicatesBuilder = new PredicatesBuilder(tableName)

    if (conditions && conditions.length > 0) {
      conditions.forEach((item: Condition) => {
        predicatesBuilder.addCondition(item.column, item.operatorType, item.values)
      })
    }

    let result = {
      ...values
    }

    // 检测是否存在 对象 one2one one2many many2manay
    const structs = TableStructCache.structMaps.get(className)
    for (let index = 0; index < structs.length; index++) {
      const item = structs[index]
      if (item.relType == RelTypeEnum.ONE_TO_ONE && typeof result[item.name] == "object") { // 一对一
        // 存储对象 获取对象id
        result[item.columnName] = await this.update(item.targetObj, values[item.name])
        delete result[item.name]
      } else if (typeof result[item.name] == "object" && item.type == ColumnType.TEXT) {
        // 当需要将对象存储为TEXT时，自动将其json格式
        result[item.name] = JSON.stringify(result[item.name])
        structs[index].propertyType = "object"
      }
    }

    return this.rdbStore.update(result, predicatesBuilder.build())
  }

  /**
   * 删除
   * @param clazz 直接使用类，如果类使用@Observed修饰后，此时需要传入字符串“类名”,例如：Person被Observed修饰后，需要改成"Person"传入
   * 会导致类的结构发生变化，
   * 后续鸿蒙应该会修复
   * @param conditions
   * @returns
   */
  public async delete<T>(clazz: T | string, conditions?: Condition[]): Promise<number> {
    let className = undefined
    if (typeof clazz == "string") {
      className = clazz
    } else {
      // @ts-ignore
      className = clazz.name
    }
    const tableName = TableStructCache.tableMaps.get(className)
    await this.checkClassTable(className)
    const predicatesBuilder: PredicatesBuilder = new PredicatesBuilder(tableName)
    if (conditions && conditions.length > 0) {
      conditions.forEach((item: Condition) => {
        predicatesBuilder.addCondition(item.column, item.operatorType, item.values)
      })
    }
    return this.rdbStore.delete(predicatesBuilder.build())
  }

  /**
   * 执行包含指定参数但不返回值的SQL语句
   * @param sql
   * @param params
   * @returns
   */
  public executeSql(sql: string, params: Array<relationalStore.ValueType>): Promise<void> {
    return this.rdbStore.executeSql(sql, params)
  }

  /**
   * 结果解析
   * @param resultSet
   * @param structs
   */
  private async parseResult<T>(resultSet: relationalStore.ResultSet, structs: Array<TableStruct>,
    parse?: (key: string, value: object) => object): Promise<T[]> {
    let rows = new Array<T>()
    while (!resultSet.isAtLastRow) {
      resultSet.goToNextRow()
      let row = {}

      for (let columIndx = 0; columIndx < resultSet.columnNames.length; columIndx++) {
        const columnName = resultSet.columnNames[columIndx]
        for (let strIndex = 0; strIndex < structs.length; strIndex++) {
          const item: TableStruct = structs[strIndex]
          let value = undefined
          if (item.columnName == columnName) {
            if (item.relType == RelTypeEnum.ONE_TO_ONE) {
              // let table = Reflect.getMetadata(AnnotationEnum.TABLE, item.targetObj)
              row[item.name] =
                await this.getById(item.targetObj, resultSet.getLong(resultSet.getColumnIndex(columnName)))
            } else if (item.type == ColumnType.INTEGER) {
              value = resultSet.getLong(resultSet.getColumnIndex(columnName))
              if (parse) {
                row[columnName] = parse(columnName, value)
              } else {
                row[columnName] = value
              }
            } else if (item.type == ColumnType.REAL) {
              value = resultSet.getDouble(resultSet.getColumnIndex(columnName))
              if (parse) {
                row[columnName] = parse(columnName, value)
              } else {
                row[columnName] = value
              }
            } else if (item.type == ColumnType.BLOB
              || item.type == ColumnType.DATE
              || item.type == ColumnType.TIME
              || item.type == ColumnType.DATETIME) {
              value = resultSet.getString(resultSet.getColumnIndex(columnName))
              if (parse) {
                row[columnName] = parse(columnName, value)
              } else {
                row[columnName] = value
              }
            } else if (item.propertyType == "object" && item.type == ColumnType.TEXT) {
              value = resultSet.getString(resultSet.getColumnIndex(columnName))
              if (parse) {
                row[columnName] = parse(columnName, value)
              } else {
                if (value == "") { // 相当于 当前对象为空对象
                  row[columnName] = undefined
                } else {
                  row[columnName] = JSON.parse(value)
                }
              }
            } else if (item.type == ColumnType.TEXT) {
              value = resultSet.getString(resultSet.getColumnIndex(columnName))
              if (parse) {
                row[columnName] = parse(columnName, value)
              } else {
                row[columnName] = value
              }
            }

          }
        }
      }

      rows.push(JSON.parse(JSON.stringify(row)) as T)
    }
    return rows
  }

  /**
   * 检查表是否存在
   * @param className
   */
  async checkClassTable(className: string) {
    // let tableName = Reflect.getMetadata(AnnotationEnum.TABLE, clazz)
    // let tableName = instance[AnnotationEnum.TABLE]

    try {
      const flag = TableStructCache.tableStatusMaps.get(className)
      if (!flag) {
        const sql = SqlBuilder.getCreateTabelSql(className)
        await this.rdbStore.executeSql(sql)
        TableStructCache.tableStatusMaps.set(className, true)
      }
    } catch (e) {
      console.error(JSON.stringify(e))
    }
  }

  /**
   * 校验所有表是否存在，不存在则创建
   */
  async checkAllTables() {
    TableStructCache.tableMaps.forEach(async (tableName, className) => {
      CJLogUtil.info(TAG, "表名：", JSON.stringify(tableName))
      await this.checkClassTable(className)
    })
  }

  test<T>(clzz: T) {
    // 如何创建 clzz 对象
  }
}

const cjdb: CJDBUtil = CJDBUtil.getSingleton()

export { cjdb as CJDBUtil }